//享元模式
//    亦称： 缓存、 Cache、 Flyweight
//    意图
//
//享元模式是一种结构型设计模式， 它摒弃了在每个对象中保存所有数据的方式， 通过共享多个对象所共有的相同状态， 让你能在有限的内存容量中载入更多对象。
#include <iostream>
#include <unordered_map>

/**
 * Flyweight Design Pattern
 *
 * Intent: Lets you fit more objects into the available amount of RAM by sharing
 * common parts of state between multiple objects, instead of keeping all of the
 * data in each object.
 */

struct SharedState
{
  std::string brand_;
  std::string model_;
  std::string color_;

  SharedState(const std::string &brand, const std::string &model, const std::string &color)
      : brand_(brand), model_(model), color_(color)
  {
  }

  friend std::ostream &operator<<(std::ostream &os, const SharedState &ss)
  {
    return os << "[ " << ss.brand_ << " , " << ss.model_ << " , " << ss.color_ << " ]";
  }
};

struct UniqueState
{
  std::string owner_;
  std::string plates_;

  UniqueState(const std::string &owner, const std::string &plates)
      : owner_(owner), plates_(plates)
  {
  }

  friend std::ostream &operator<<(std::ostream &os, const UniqueState &us)
  {
    return os << "[ " << us.owner_ << " , " << us.plates_ << " ]";
  }
};

/**
 * The Flyweight stores a common portion of the state (also called intrinsic
 * state) that belongs to multiple real business entities. The Flyweight accepts
 * the rest of the state (extrinsic state, unique for each entity) via its
 * method parameters.
 */
class Flyweight
{
private:
  SharedState *shared_state_;

public:
  Flyweight(const SharedState *shared_state) : shared_state_(new SharedState(*shared_state))
  {
  }
  Flyweight(const Flyweight &other) : shared_state_(new SharedState(*other.shared_state_))
  {
  }
  ~Flyweight()
  {
    delete shared_state_;
  }
  SharedState *shared_state() const
  {
    return shared_state_;
  }
  void Operation(const UniqueState &unique_state) const
  {
    std::cout << "Flyweight: Displaying shared (" << *shared_state_ << ") and unique (" << unique_state << ") state.\n";
  }
};
/**
 * The Flyweight Factory creates and manages the Flyweight objects. It ensures
 * that flyweights are shared correctly. When the client requests a flyweight,
 * the factory either returns an existing instance or creates a new one, if it
 * doesn't exist yet.
 */
class FlyweightFactory
{
  /**
   * @var Flyweight[]
   */
private:
  std::unordered_map<std::string, Flyweight> flyweights_;
  /**
   * Returns a Flyweight's string hash for a given state.
   */
  std::string GetKey(const SharedState &ss) const
  {
    return ss.brand_ + "_" + ss.model_ + "_" + ss.color_;
  }

public:
  FlyweightFactory(std::initializer_list<SharedState> share_states)
  {
    for (const SharedState &ss : share_states)
    {
      this->flyweights_.insert(std::make_pair<std::string, Flyweight>(this->GetKey(ss), Flyweight(&ss)));
    }
  }

  /**
   * Returns an existing Flyweight with a given state or creates a new one.
   */
  Flyweight GetFlyweight(const SharedState &shared_state)
  {
    std::string key = this->GetKey(shared_state);
    if (this->flyweights_.find(key) == this->flyweights_.end())
    {
      std::cout << "FlyweightFactory: Can't find a flyweight, creating new one.\n";
      this->flyweights_.insert(std::make_pair(key, Flyweight(&shared_state)));
    }
    else
    {
      std::cout << "FlyweightFactory: Reusing existing flyweight.\n";
    }
    return this->flyweights_.at(key);
  }
  void ListFlyweights() const
  {
    size_t count = this->flyweights_.size();
    std::cout << "\nFlyweightFactory: I have " << count << " flyweights:\n";
    for (std::pair<std::string, Flyweight> pair : this->flyweights_)
    {
      std::cout << pair.first << "\n";
    }
  }
};

// ...
void AddCarToPoliceDatabase(
    FlyweightFactory &ff, const std::string &plates, const std::string &owner,
    const std::string &brand, const std::string &model, const std::string &color)
{
  std::cout << "\nClient: Adding a car to database.\n";
  const Flyweight &flyweight = ff.GetFlyweight({brand, model, color});
  // The client code either stores or calculates extrinsic state and passes it
  // to the flyweight's methods.
  flyweight.Operation({plates, owner});
}

/**
 * The client code usually creates a bunch of pre-populated flyweights in the
 * initialization stage of the application.
 */

int main()
{
  FlyweightFactory *factory = new FlyweightFactory({{"Chevrolet", "Camaro2018", "pink"}, {"Mercedes Benz", "C300", "black"}, {"Mercedes Benz", "C500", "red"}, {"BMW", "M5", "red"}, {"BMW", "X6", "white"}});
  factory->ListFlyweights();

  AddCarToPoliceDatabase(*factory,
                         "CL234IR",
                         "James Doe",
                         "BMW",
                         "M5",
                         "red");

  AddCarToPoliceDatabase(*factory,
                         "CL234IR",
                         "James Doe",
                         "BMW",
                         "X1",
                         "red");
  factory->ListFlyweights();
  delete factory;

  return 0;
}